package com.objectcarpentry.kaylee.gstreamer;

import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;

import org.gstreamer.ClockTime;
import org.gstreamer.Element;
import org.gstreamer.ElementFactory;
import org.gstreamer.GstObject;
import org.gstreamer.Bus.EOS;
import org.gstreamer.Bus.ERROR;
import org.gstreamer.elements.PlayBin;

import com.objectcarpentry.kaylee.IClockTime;
import com.objectcarpentry.kaylee.IPlayable;
import com.objectcarpentry.kaylee.IPlayer;
import com.objectcarpentry.kaylee.IPlayerEvent;
import com.objectcarpentry.kaylee.IPlayerListener;
import com.objectcarpentry.kaylee.PlayerEventType;
import com.objectcarpentry.kaylee.State;

public class KayleeGStreamerPlayer implements IPlayer {
	
	
	private PlayBin playBin;
	
	private Element audioSink;
	
	private boolean mute;
	
	private State state;
	
	private Collection<IPlayerListener> listeners;

	private URI input;
	
	public KayleeGStreamerPlayer()  {
		playBin = new PlayBin("AudioPlayer");
		playBin.setVideoSink(ElementFactory.make("fakesink", "videosink"));
		GStreamerListener listener = new GStreamerListener();
		playBin.getBus().connect((EOS) listener);
		playBin.getBus().connect((ERROR) listener);
		audioSink = playBin.getElementByName("audiosink");
		state = State.STOPPED;
	}
	
	@Override
	public void setVolume(double volume){
		playBin.setVolume(volume);
	}

	public void play(IPlayable p, Object parameters) {
		p.playOn(this, parameters);
	}

	private Collection<IPlayerListener> getListeners() {
		if (listeners == null){
			listeners = new HashSet<IPlayerListener>();
		}
		return listeners;
	}
	
	public synchronized void addPlayerListener(IPlayerListener listener){
		getListeners().add(listener);
	}
	
	
	public synchronized void mute(boolean b){
		if (mute != b){
			mute = b;
			Element sink = mute ? null : audioSink;
			playBin.setAudioSink(sink);
		}
	}


	@Override
	public int getMode() {
		return 0;
	}

	@Override
	public void pause() {
		if (state == State.PLAYING){
			state = State.PAUSED;
			fireEvent(PlayerEventType.PAUSE);
			playBin.setState(org.gstreamer.State.PAUSED);
		}
	}

	@Override
	public void play() {
		if (input != null && state != State.PLAYING){
			state = State.PLAYING;
			playBin.setState(org.gstreamer.State.PLAYING);
		}
		fireEvent(PlayerEventType.PLAY);
	}

	@Override
	public void removePlayerListener(IPlayerListener l) {
		if (listeners != null){
			listeners.remove(l);
		}
	}

	@Override
	public void setInput(URI uri) {
		input = uri;
		fireEvent(PlayerEventType.INPUT);
		org.gstreamer.State old = playBin.getState();
		playBin.setState(org.gstreamer.State.NULL);
		playBin.setURI(input);
		playBin.setState(old);
	}

	@Override
	public void setMode(int mode) {}

	@Override
	public void stop() {
		if (state != State.STOPPED){
			playBin.setState(org.gstreamer.State.NULL);
			state = State.STOPPED;
			fireEvent(PlayerEventType.STOP);
		}
	}
	
	class GStreamerListener implements EOS , ERROR {
		@Override
		public void endOfStream(GstObject source) {
			System.out.println("endofstream");
			fireEvent(PlayerEventType.EOS);
		}

		@Override
		public void errorMessage(GstObject source, int code, String message) {
			fireEvent(PlayerEventType.ERROR);
		}
	}
	
	
	protected void fireEvent(final PlayerEventType type){
		IPlayerEvent e = new IPlayerEvent(){
			@Override
			public IPlayer getPlayer() {
				return KayleeGStreamerPlayer.this;
			}
			@Override
			public PlayerEventType getType() {
				return type;
			}
		};
		for (IPlayerListener l : new ArrayList<IPlayerListener>(getListeners())){
			l.handleEvent(e);
		}
	}

	@Override
	public double getVolume() {
		return playBin.getVolume();
	}
	
	public void dispose(){
		playBin.setState(org.gstreamer.State.NULL);
	}
	
	@Override 
	public URI getInput(){
		return input;
	}

	@Override
	public State getState() {
		return state;
	}
	
	@Override
	public IClockTime getTime(){
		return new IClockTime() {
			
			final ClockTime ct = playBin.queryPosition();

			@Override
			public long get() {
				return ct.getNanoSeconds();
			}

			@Override
			public int getHours() {
				return (int) ct.getHours();
			}

			@Override
			public int getMinutes() {
				return (int) ct.getMinutes();
			}

			@Override
			public int getSeconds() {
				return (int) ct.getSeconds();
			}
			
		};
	}
	
}